package event

import (
	"context"
	"strings"

	nftCommon "gitcode.net/togolife/nfttoken/common"
	"gitcode.net/togolife/nfttoken/contract"
	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
)

var evtLog = nftCommon.GetNftLog("event")

type LogEmit struct {
	Type           int
	Chain          string
	ContractAddr   string
	TxHash         string
	Transfer       *contract.ContractTransfer
	Approval       *contract.ContractApproval
	ApprovalForAll *contract.ContractApprovalForAll
}

func SubscribeEvent(chain string, info nftCommon.ChainInfo, exitFlag <-chan int, event chan<- LogEmit) error {
	client, err := ethclient.Dial(info.NodeWssPath)
	if err != nil {
		evtLog.LogE("Create subscribe event client failed! [%v]", err)
		return err
	}
	nftContract, err := contract.NewContract(common.Address{}, client)
	if err != nil {
		evtLog.LogE("Get nft contract failed! [%v]", err)
		return err
	}
	filterAddress := []common.Address{}
	for _, v := range info.WatchAddress {
		filterAddress = append(filterAddress, common.HexToAddress(v))
	}
	query := ethereum.FilterQuery{
		Addresses: filterAddress,
	}
	logs := make(chan types.Log)
	sub, err := client.SubscribeFilterLogs(context.Background(), query, logs)
	if err != nil {
		evtLog.LogE("Subscribe to client failed! [%v]", err)
		return err
	}
	go dealContractEvent(chain, nftContract, sub, logs, exitFlag, event)
	return nil
}

func dealContractEvent(chain string, nftContract *contract.Contract, sub ethereum.Subscription, logs chan types.Log, exitFlag <-chan int, event chan<- LogEmit) {
	for {
		select {
		case _ = <-exitFlag:
			evtLog.LogI("Got exit message, exit!")
			return
		case err := <-sub.Err():
			evtLog.LogE("Subscription failed, exit! [%v]", err)
			var emit LogEmit
			emit.Type = -1
			emit.Chain = chain
			event <- emit
			return
		case log := <-logs:
			decodeContractLog(chain, nftContract, log, event)
		}
	}
}

func decodeContractLog(chain string, nftContract *contract.Contract, log types.Log, event chan<- LogEmit) {
	logTransferSig := []byte("Transfer(address,address,uint256)")
	logApprovalSig := []byte("Approval(address,address,uint256)")
	logApprovalForAllSig := []byte("ApprovalForAll(address,address,bool)")
	logTransferSigHash := crypto.Keccak256Hash(logTransferSig)
	logApprovalSigHash := crypto.Keccak256Hash(logApprovalSig)
	logApprovalForAllSigHash := crypto.Keccak256Hash(logApprovalForAllSig)
	var emit LogEmit
	switch strings.ToLower(log.Topics[0].Hex()) {
	case strings.ToLower(logTransferSigHash.Hex()):
		v, err := nftContract.ParseTransfer(log)
		if err != nil {
			evtLog.LogE("Unpack Transfer failed! err [%v], log [%v]", err, log)
			emit.Type = -2
		} else {
			emit.Transfer = v
			emit.Type = 1
		}
		emit.TxHash = log.TxHash.String()
		emit.ContractAddr = log.Address.String()
		emit.Chain = chain
	case strings.ToLower(logApprovalSigHash.Hex()):
		v, err := nftContract.ParseApproval(log)
		if err != nil {
			evtLog.LogE("Unpack Approval failed! err [%v], log [%v]", err, log)
			emit.Type = -2
		} else {
			emit.Approval = v
			emit.Type = 2
		}
		emit.TxHash = log.TxHash.String()
		emit.ContractAddr = log.Address.String()
		emit.Chain = chain
	case strings.ToLower(logApprovalForAllSigHash.Hex()):
		v, err := nftContract.ParseApprovalForAll(log)
		if err != nil {
			evtLog.LogE("Unpack ApprovalForAll failed! err [%v], log [%v]", err, log)
			emit.Type = -2
		} else {
			emit.ApprovalForAll = v
			emit.Type = 3
		}
		emit.TxHash = log.TxHash.String()
		emit.ContractAddr = log.Address.String()
		emit.Chain = chain
	default:
		evtLog.LogI("Got unhandler log [%v]", log)
		return
	}
	evtLog.LogD("Emit message [%v]", emit)
	event <- emit
	return
}
